%include "Boot/BootProperty.S"
%include "Boot/Segmentation.S"
%include "Boot/PagedSegmentation.S"
%include "Boot/PageAttributes.S"
org LOADER_CODE_BASE
bits 16
jmp Boot.Loader.Entrypoint
%include "Boot/RealModeUtility/Halt.asm"
%include "Boot/RealModeUtility/Print.asm"
%include "Boot/RealModeUtility/GetPhysicalMemoryInfo.asm"
%include "Boot/RealModeUtility/ZeroMemory16.asm"
%include "Boot/ProtectedModeUtility/Print.asm"
%include "Boot/ProtectedModeUtility/Halt.asm"
%include "Boot/ProtectedModeUtility/ReadDisk.asm"
%include "Boot/ProtectedModeUtility/ResolveELF32.asm"
%include "Boot/ProtectedModeUtility/VideoTextModeLocateCursor.asm"
%include "Boot/ProtectedModeUtility/VideoTextModePositionCursor.asm"
%include "Boot/ProtectedModeUtility/ZeroMemory32.asm"
%include "Boot/ProtectedModeUtility/InstallContiguousPTEs.asm"

bits 16
Boot.Loader.Entrypoint:
    xor ax, ax
    mov ds, ax
    mov ss, ax
    mov esp, LOADER_CODE_BASE ; 用 esp 的目的是同时清零 esp 高位
Boot.Loader.DetectMemory:
    push word ARDS_TABLE_SIZE
    push word ARDS_TABLE_BASE
    call Boot.Memory.GetPhysicalMemoryInfo
    add sp, 4
; 打开20号地址线
Boot.Loader.Enable_Addr20:
    in al, 0x92
    or al, 0b0000_0010
    out 0x92, al
; 初始化 GDT
Boot.Loader.Initialize_GDT:
    cli ; QEMU 必须在进入实模式之前关中断
    mov bx, GDT_BASE ; GDT 基址
    push word GDT_SIZE
    push word GDT_BASE
    call Boot.Memory.ZeroMemory16
    add sp, 4
    mov dword [bx], 0
    mov dword [bx + 4], 0
    [warning -number-overflow] ; 关闭警告：以下代码会报溢出警告，是有意为之，其目的是将 64 位的段描述符分 2 次存入内存中
    mov dword [bx + 8], CODE_SEGMENT_DESCRIPTOR
    mov dword [bx + 12], CODE_SEGMENT_DESCRIPTOR >> 32
    mov dword [bx + 16], DATA_SEGMENT_DESCRIPTOR
    mov dword [bx + 20], DATA_SEGMENT_DESCRIPTOR >> 32
    mov dword [bx + 24], GRAPHIC_COLOR_MODE_DATA_SEGMENT_DESCRIPTOR
    mov dword [bx + 28], GRAPHIC_COLOR_MODE_DATA_SEGMENT_DESCRIPTOR >> 32
    mov dword [bx + 32], GRAPHIC_BLACKWHITE_MODE_DATA_SEGMENT_DESCRIPTOR
    mov dword [bx + 36], GRAPHIC_BLACKWHITE_MODE_DATA_SEGMENT_DESCRIPTOR >> 32
    mov dword [bx + 40], GRAPHIC_TEXT_MODE_DATA_SEGMENT_DESCRIPTOR
    mov dword [bx + 44], GRAPHIC_TEXT_MODE_DATA_SEGMENT_DESCRIPTOR >> 32
    [warning +number-overflow] ; 开启溢出警告
    push dword GDT_BASE ; GDT 基址
    push word GDT_LIMIT ; GDT 界限
    mov bp, sp ; 保护模式下基址寻址只能用 bx 和 bp
    lgdt [bp]
    add sp, 6
;; cr0 第一位置位
Boot.Loader.EnableProtectionBit:
    mov eax, cr0
    or eax, 0b0000_0001
    mov cr0, eax
Boot.Loader.RefreshPipeline:
    jmp dword SEGMENT_SELECTOR_CODE:Boot.Loader.EnterProtectedMode
bits 32
Boot.Loader.EnterProtectedMode:
    mov ax, SEGMENT_SELECTOR_DATA
    mov ds, ax
    mov ss, ax
    mov es, ax
    mov fs, ax
    mov ax, SEGMENT_SELECTOR_GRAPHIC_TEXT_MODE
    mov gs, ax
    mov esp, LOADER_STACK_BASE
    mov ebp, esp
    call VideoTextModeLocateCursor
    mov ebx, eax
    shl eax, 1
    push dword 0b0000_1010
    push dword Boot.Loader.EnterProtectedMode.OK_MSG
    push dword eax
    call Boot.ProtectedModeUtility.Print
    add esp, 4 * 3
    add ebx, 80
    push ebx
    call VideoTextModePositionCursor
    add esp, 4
Boot.Loader.PageMemory:
    ; 页目录表、768 号页表清零
    push PAGE_SIZE
    push PAGE_DIRECTORY_BASE
    call Boot.Memory.ZeroMemory32
    add esp, 8
    push PAGE_SIZE
    push PAGE_TABLE_768_BASE
    call Boot.Memory.ZeroMemory32
    add esp, 8
    ; 设置页目录第 0 项、第 768 项、第 1022 项、1023 项
    mov dword [PAGE_DIRECTORY_BASE + 0 * 4], PAGE_TABLE_768_BASE | PDE_PRESENT | PDE_WRITEABLE
    mov dword [PAGE_DIRECTORY_BASE + 768 * 4], PAGE_TABLE_768_BASE | PDE_PRESENT | PDE_WRITEABLE
    mov dword [PAGE_DIRECTORY_BASE + 1022 * 4], PAGE_DIRECTORY_BASE | PDE_PRESENT | PDE_WRITEABLE
    mov dword [PAGE_DIRECTORY_BASE + 1023 * 4], PAGE_DIRECTORY_BASE | PDE_PRESENT | PDE_WRITEABLE
; 建立 768 号页表
bits 32
Boot.Memory.InstallPageTable768:
    mov ebx, PAGE_TABLE_768_BASE
    ; IVT, BIOS Data, bootloader
    mov eax, 0x00
    or eax, PTE_PRESENT | PTE_WRITEABLE
    mov [PAGE_TABLE_768_BASE + 0 * 4], eax
    ; EDBA
    mov eax, 0x9F_000
    or eax, PTE_PRESENT
    mov [PAGE_TABLE_768_BASE + 1 * 4], eax
    ; 显存 彩色
    push dword 16
    push dword PTE_PRESENT | PTE_WRITEABLE
    push dword 0xA0_000
    push dword PAGE_TABLE_768_BASE + 2 * 4
    call Boot.Memory.InstallContiguousPTEs
    add esp, 4 * 4
    ; 显存 黑白
    push dword 8
    push dword PTE_PRESENT | PTE_WRITEABLE
    push dword 0xB0_000
    push dword PAGE_TABLE_768_BASE + 18 * 4
    call Boot.Memory.InstallContiguousPTEs
    add esp, 4 * 4
    ; 显存 文本
    push dword 8
    push dword PTE_PRESENT | PTE_WRITEABLE
    push dword 0xB8_000
    push dword PAGE_TABLE_768_BASE + 26 * 4
    call Boot.Memory.InstallContiguousPTEs
    add esp, 4 * 4
    ; 显示适配器 BIOS
    push dword 8
    push dword PTE_PRESENT
    push dword 0xC0_000
    push dword PAGE_TABLE_768_BASE + 34 * 4
    call Boot.Memory.InstallContiguousPTEs
    add esp, 4 * 4
    ; 外围硬件 ROM
    push dword 40
    push dword PTE_PRESENT | PTE_PAGE_LEVEL_CACHE_DISABLE
    push dword 0xC8_000
    push dword PAGE_TABLE_768_BASE + 42 * 4
    call Boot.Memory.InstallContiguousPTEs
    add esp, 4 * 4
    ; BIOS
    push dword 16
    push dword PTE_PRESENT | PTE_PAGE_LEVEL_CACHE_DISABLE
    push dword 0xF0_000
    push dword PAGE_TABLE_768_BASE + 82 * 4
    call Boot.Memory.InstallContiguousPTEs
    add esp, 4 * 4
    ; ARDST, GDT
    mov eax, ARDS_TABLE_BASE
    or eax, PTE_PRESENT | PTE_WRITEABLE
    mov [PAGE_TABLE_768_BASE + 98 * 4], eax
    ; APIC
    mov eax, 0xFEE0_0000
    or eax, PTE_PRESENT | PTE_PAGE_LEVEL_CACHE_DISABLE
    mov [PAGE_TABLE_768_BASE + 99 * 4], eax
    ; 线程
    mov eax, 0x0_3000
    or eax, PTE_PRESENT | PTE_WRITEABLE
    mov [PAGE_TABLE_768_BASE + 100 * 4], eax
    ; 实模式 1 MB 剩余空间
    push dword 157
    push dword PTE_PRESENT | PTE_WRITEABLE
    push dword 0x02_000
    push dword PAGE_TABLE_768_BASE + 99 * 4
    call Boot.Memory.InstallContiguousPTEs
    add esp, 4 * 4
; 重新加载 GDT
bits 32
Boot.Loader.PagingEnable:
    sub esp, 6 ; GDTR 占 6 字节
    sgdt [esp] ; 暂存 GDTR
    mov ebp, esp
    mov ebx, dword [ebp + 2] ; 取出 GDT 基址
    [warning -number-overflow] ; 关闭警告：以下代码会报溢出警告，是有意为之，其目的是将 64 位的段描述符分 2 次存入内存中
    mov dword [ebx + 8], PAGED_CODE_SEGMENT
    mov dword [ebx + 12], PAGED_CODE_SEGMENT >> 32

    mov dword [ebx + 16], PAGED_DATA_SEGMENT
    mov dword [ebx + 20], PAGED_DATA_SEGMENT >> 32

    mov dword [ebx + 24], PAGED_VIDEO_COLOR_SEGMENT
    mov dword [ebx + 28], PAGED_VIDEO_COLOR_SEGMENT >> 32

    mov dword [ebx + 32], PAGED_VIDEO_BLACKWHITE_SEGMENT
    mov dword [ebx + 36], PAGED_VIDEO_BLACKWHITE_SEGMENT >> 32

    mov dword [ebx + 40], PAGED_VIDEO_TEXT_SEGMENT
    mov dword [ebx + 44], PAGED_VIDEO_TEXT_SEGMENT >> 32
    [warning +number-overflow] ; 开启溢出警告
    mov dword [ebp + 2], PAGED_GDT_BASE ; GDT 移入内核空间
    lgdt [esp] ; 修改 GDT 基址，GDTR 提前指向虚拟 GDT 起始地址（开启分页前不可访问）
    mov eax, PAGE_DIRECTORY_BASE
    mov cr3, eax ; cr3 保存页目录
    mov eax, cr0
    or eax, 0x8000_0000
    mov cr0, eax ; 打开 cr0.pg
    mov ebp, 0xC000_0600 ; ebp 指向内核空间
    mov esp, 0xC000_0600 ; esp 指向内核空间
    jmp dword SEGMENT_SELECTOR_CODE:Boot.Loader.PagingEnable.OK ; 刷新流水线
Boot.Loader.PagingEnable.OK:
    call VideoTextModeLocateCursor
    shl eax, 1
    push dword 0b0000_1010
    push dword Boot.Loader.Page.OK_MSG
    push dword eax
    call Boot.ProtectedModeUtility.Print
    add esp, 4 * 3
    add eax, 160
    shr eax, 1
    push eax
    call VideoTextModePositionCursor
    add esp, 4
; 允许扩展指令集
; Boot.Loader.EnableFeatureExtensions:
;     mov eax, cr4
;     or eax, 0x600
;     mov cr4, eax
; 加载内核到临时位置，对应 768 号页表 PDT 232 ~ 255，虚拟地址，共计 24 * 4 KiB
bits 32
Boot.Loader.LoadKernel:
    push dword 4112 ; dwLBA， Kernel 在磁盘中自第 12 个扇区开始存放
    push dword KERNEL_FILE_BASE; lpDstAddr，内核临时加载到 (768, 232, 0) ~ (768, 255, 0)
    push dword 192 ; 待加载扇区
    call Boot.ProtectedModeUtility.ReadDisk
    add esp, 12
    jc Boot.ProtectedModeUtility.Halt ; 出现错误
    call VideoTextModeLocateCursor ; eax = 光标位置
    shl eax, 1 ; 乘以 2 得到显存偏移
    push dword 0b0000_1010 ; 颜色
    push dword Boot.Loader.LoadKernel.OK_MSG
    push dword eax
    call Boot.ProtectedModeUtility.Print
    add esp, 4 * 3
    add eax, 160
    shr eax, 1
    push eax
    call VideoTextModePositionCursor
    add esp, 4
; 解析内核，调入内存
bits 32
Boot.Loader.ResolveKernel:
    push dword KERNEL_FILE_BASE ; lpBase 内核文件装载地址，返回时 lpBase 将被修改为重定位后的内核入口地址
    call Boot.ProtectedModeUtility.ResolveELF32 ; 解析内核文件，重定位到正确位置
    mov esp, 0xC010_0000 ; (768, 200, 0)
    ; 为内核初始化所有通用寄存器和段寄存器
    mov ebp, esp
    xor ebx, ebx
    xor ecx, ecx
    xor edx, edx
    xor esi, esi
    xor edi, edi
    ; 平坦内存模型，除 CS DS SS 外其余段寄存器清零
    mov es, cx
    mov fs, cx
    mov gs, cx
    jmp eax ; 跳转到内核代码执行
Boot.Loader.EnterProtectedMode.OK_MSG:
    db "[OK] Successfully enter into protected mode.", 0x00
Boot.Loader.Page.OK_MSG:
    db "[OK] Successfully page memory.", 0x00
Boot.Loader.LoadKernel.OK_MSG:
    db "[OK] Successfully load kernel.", 0x00
times (LOADER_CODE_SIZE - ($ - $$)) db 0
